﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using Primitive.Mvvm.Framework.Interfaces;

namespace Primitive.Mvvm.Framework.Implementations
{
    public abstract class BaseViewModel : IViewModel, IViewCallback
    {
        private IMediator _mediator;
        public virtual Func<IViewCallback, bool> RefreshViewFunc { get; set; }

        public object Sender { get; set; }

        public string PropertyName { get; set; }

        public IMediator Mediator
        {
            get { return _mediator; }
        }

        protected BaseViewModel(IMediator mediator)
        {
            _mediator = mediator;
            _mediator.Subscribe(this);
        }

        protected bool PropertyExists(dynamic bag, string propName)
        {
            if (bag == null)
                return false;

            Type typeOfDynamic = bag.GetType();
            return typeOfDynamic.GetProperties().Where(p => p.Name.Equals(propName, StringComparison.OrdinalIgnoreCase)).Any();
        }

        public void RaisePropertyChanged<T>(object sender, Expression<Func<T>> propertyExpresssion)
        {
            var propertyName = PropertySupport.ExtractPropertyName(propertyExpresssion);
            RaisePropertyChanged(sender, propertyName);
        }

        public void RaisePropertyChanged(object sender, string propertyName)
        {
            VerifyPropertyName(propertyName);

            if(IsKnownProperty(propertyName))
                _mediator.NotifyAll(sender, new PropertyChangedEventArgs(propertyName));
        }

        public virtual void NotifyViewModelChanged(object sender, PropertyChangedEventArgs pce)
        {
            if(sender != this && !string.IsNullOrEmpty(pce.PropertyName) && RefreshViewFunc != null)
            {
                Sender = sender;
                PropertyName = pce.PropertyName;
                RefreshViewFunc(this);
            }
        }

        private bool IsKnownProperty(string propertyName)
        {
            return
                GetType().GetProperties().FirstOrDefault(
                    p => p.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase)) != null;
        }

        /// <summary>
        /// Warns the developer if this Object does not have a public property with
        /// the specified name. This method does not exist in a Release build.
        /// </summary>
        [Conditional("DEBUG")]
        [DebuggerStepThrough]
        public void VerifyPropertyName(String propertyName)
        {
            // verify that the property name matches a real,  
            // public, instance property on this Object.
            if (TypeDescriptor.GetProperties(this)[propertyName] == null)
            {
                Debug.Fail("Invalid property name: " + propertyName);
            }
        }

        public void Dispose()
        {
            _mediator.Unsubscribe(this);
            _mediator = null;
            RefreshViewFunc = null;
        }

        public bool CanRefreshView(IViewCallback callback)
        {
            if (callback == null)
                return false;

            if (callback.Sender == this)
                return false;

            return PropertyExists(this, callback.PropertyName);
        }
    }
}
